import Recorder from 'recorder-core';
import { throttle } from 'lodash';
import getPreAuth from './getPreAuth';
import XHRUploadService from './XHRUploadService';
import { formatTime } from './utils';

import 'recorder-core/src/engine/mp3';
import 'recorder-core/src/engine/mp3-engine';

/**
 * 录制并实时上传音频
 * @param {Number} SendInterval 发送间隔时间 && 音频片段长度
 * @param {String} onLinePartLink 线上部分链接，用于在暂存后续传
 * @param {Function} uploadRecInfoFunc 追加上传成功后，上传数据到服务器的函数
 */
class RealtimeUploader {
    constructor(
        durationCallBack,
        volumeCallBack,
        stateChangeCallback,
        uplodateStateChangeCallback,
        uploadFailedCallback,
        onLinePartLink,
        SendInterval = 3000,
        ossFileName,
        ossKeyName = '',
        uploadRecInfoFunc,
        onLinePartDuration
    ) {
        this.continuousUpLoadFailTimes = 0; // 上传连续失败的次数
        this.uploadFailedCallback = uploadFailedCallback;
        this.uplodateStateChangeCallback = uplodateStateChangeCallback;
        this.uploadRecInfoFunc = uploadRecInfoFunc;
        this.onLinePartDuration = onLinePartDuration; // 已上传部分的时间长度(ms),用于页面刷新后获取时间长度
        console.log('onLinePartDuration', this.onLinePartDuration);
        this.lastBufferDuration = 0; // 前一次onProcess 回调时的bufferduration
        this.type = 'mp3';
        this.sampleRate = 16000; // 采样率
        this.bitRate = 16; // 码率，2 Byte/s
        this.cachedBlobClipArray = []; // 已缓存的音频blob数组
        this.SendInterval = SendInterval; // 发送间隔时间 && 音频片段长度
        this.myRecorderState = -1; // 录音状态, -1 未初始化, 0停止， 1录制中， 2暂停
        this.rec = null; // 保存自身实例
        this.durationCallBack = durationCallBack; // 时间变化的回调
        this.volumeCallBack = volumeCallBack; // 音量变化回调
        this.stateChangeCallback = stateChangeCallback; // 播放状态变化回调
        // oss 相关参数
        this.ossFileName = ''; // oss获取预授权所需文件名
        this.ossKeyName = ossKeyName; // oss上传所需keyName，需要从预授权获取
        // 实时上传相关参数
        this.realTimeSendTryTimestamp = 0; // RealTimeEncodeAndSend 方法最后一次被调用的时间
        this.realTimeSendTryEncBusy = 0;
        this.realTimeSendTryNumber = 0;
        this.transferUploadNumberMax = 0;
        this.realTimeSendTryChunk = null;
        this.uploadingPromise = null; // 保存上传过程的Promise，用于鉴定是否需要重启上传服务
        this.onLinePartLink = onLinePartLink; // 已暂存部分的链接
        this.hasOnlineMetaData = false; // 是否已获取线上部分的metadata，用于续传和播放。
        if (onLinePartLink) {
            this.getMetaDataFromServer();
        }
        if (!ossFileName) {
            this.ossFileName = `interviewUpload-${+new Date()}.mp3`; //
        } else {
            this.ossFileName = ossFileName;
        }
        // 新版转码方式
        this.realTimeSendTryTime = 0;
        this.realTimeSendTryNumber = 0;
        this.transferUploadNumberMax = 0;
        this.realTimeSendTryBytesChunks = [];
        this.realTimeSendTryClearPrevBufferIdx = 0;
    }

    set recorderState(val) {
        if ((val === 0 || val === 2) && this.myRecorderState === 1) {
            // 从播放变到 暂停 or 停止，肯定需要上传
            this.uplodateStateChangeCallback(true);
        }
        this.myRecorderState = val;
        console.log('录音状态变化:', val);

        if (this.stateChangeCallback) {
            this.stateChangeCallback(this.myRecorderState);
        }
    }

    get recorderState() {
        return this.myRecorderState;
    }

    /**
     * =====实时处理时清理一下内存（延迟清理），本方法先于RealTimeSendTry执行======
     */
    RealTimeOnProcessClear(buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx) {
        if (this.realTimeSendTryTime === 0) {
            this.realTimeSendTryTime = Date.now();
            this.realTimeSendTryNumber = 0;
            this.transferUploadNumberMax = 0;
            this.realTimeSendTryBytesChunks = [];
            this.realTimeSendTryClearPrevBufferIdx = 0;
        }

        // 清理PCM缓冲数据，最后完成录音时不能调用stop，因为数据已经被清掉了
        // 这里进行了延迟操作（必须要的操作），只清理上次到现在的buffer
        for (let i = this.realTimeSendTryClearPrevBufferIdx; i < newBufferIdx; i += 1) {
            buffers[i] = null;
        }
        this.realTimeSendTryClearPrevBufferIdx = newBufferIdx;
    }

    TransferUpload(number, blobOrNull) {
        this.transferUploadNumberMax = Math.max(this.transferUploadNumberMax, number);
        if (blobOrNull) {
            // *********Blob***************
            // 可以实现
            // WebSocket send(blob) ...
            // WebRTC send(blob) ...
            // XMLHttpRequest send(blob) ...
            console.log('TransferUpload，压入数组，等上传：');
            this.cachedBlobClipArray.push(blobOrNull);
            this.startUpload();
        }
    }

    /**
     * 实时转码并调用上传
     * @param {*} isClose 是否为关闭
     */
    RealTimeSendTry(chunkBytes, isClose) {
        if (chunkBytes) {
            // 推入缓存再说
            this.realTimeSendTryBytesChunks.push(chunkBytes);
        }

        const t1 = Date.now();
        if (!isClose && t1 - this.realTimeSendTryTime < this.SendInterval) {
            return; // 控制缓冲达到指定间隔才进行传输(仅限在录制中的情况，暂停、停止，直接上传)
        }
        this.realTimeSendTryTime = t1;
        this.realTimeSendTryNumber += 1;
        const number = this.realTimeSendTryNumber;

        // mp3缓冲的chunk拼接成一个更长点的mp3
        let len = 0;
        for (let i = 0; i < this.realTimeSendTryBytesChunks.length; i += 1) {
            len += this.realTimeSendTryBytesChunks[i].length;
        }
        const chunkData = new Uint8Array(len);
        for (let i = 0, idx = 0; i < this.realTimeSendTryBytesChunks.length; i += 1) {
            const chunk = this.realTimeSendTryBytesChunks[i];
            chunkData.set(chunk, idx);
            idx += chunk.length;
        }
        this.realTimeSendTryBytesChunks = [];

        // 推入传输
        let blob = null;
        let meta = {};
        if (chunkData.length > 0) {
            // mp3不是空的
            blob = new Blob([chunkData], { type: 'audio/mp3' });
            meta = Recorder.mp3ReadMeta([chunkData.buffer], chunkData.length) || {}; // 读取出这个mp3片段信息
        }
        this.TransferUpload(
            number,
            blob,
            meta.duration || 0,
            {
                set: {
                    type: 'mp3',
                    sampleRate: meta.sampleRate,
                    bitRate: meta.bitRate
                }
            },
            isClose
        );
        this.realTimeSendTryWavTestBuffers = [];
    }

    handleDisconnected() {
        console.log('处理断网。');
        if (this.myRecorderState === 1) {
            this.pauseRec();
            this.cachedBlobClipArray.length = 0;
        }
    }

    /**
     * 获取oss中已保存音频长度，从服务端数据中可获取。
     */
    async getMetaDataFromServer() {
        console.log(this);
    }

    /**
     * 检查上传失败次数
     */
    checkFailedTimes(isFail = false) {
        if (!isFail) {
            this.continuousUpLoadFailTimes = 0;
            this.uploadFailedCallback(false);
        } else {
            this.continuousUpLoadFailTimes += 1;
            if (this.continuousUpLoadFailTimes >= 3) {
                this.uploadFailedCallback(true);
                this.continuousUpLoadFailTimes = 0;
            }
        }
    }

    /**
     * 重置状态,应对重复启动
     */
    RealTimeSendTryReset() {
        this.realTimeSendTryTimestamp = 0;
    }

    /**
     * 开始实时录制与上传
     */
    startRecAndUpload() {
        this.rec.start();
        this.recorderState = 1;
        this.RealTimeSendTryReset(); // 重置,应对重复启动
    }

    /**
     * 停止录音，上传无法停止，必须等到 cachedBlobClipArray 数组清空。
     */
    stopRec() {
        if (this.rec) {
            this.rec.close(); // 直接close掉即可，这个例子不需要获得最终的音频文件
            this.RealTimeSendTry(null, true); // 最后一次发送
        }
        this.recorderState = 0;
    }

    /**
     * 暂停录音
     */
    pauseRec() {
        this.rec.pause();
        this.recorderState = 2;
        this.RealTimeSendTry(null, true); // 最后一次发送
    }

    /**
     * 继续录音
     */
    resumeRec(duration) {
        if (duration) {
            this.onLinePartDuration = duration;
        }
        this.rec.resume();
        this.recorderState = 1;
    }

    /**
     * 初始化 recorder-core 实例
     * 提供重新设置 keyName 和 onLinePartDuration的能力
     */
    initRecInstance(keyName, onLinePartDuration) {
        if (keyName) {
            this.ossKeyName = keyName;
        }
        if (onLinePartDuration) {
            this.onLinePartDuration = onLinePartDuration;
        }
        return new Promise((resolve, reject) => {
            this.rec = Recorder({
                type: this.type,
                sampleRate: this.sampleRate,
                bitRate: this.bitRate,
                onProcess: throttle((buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) => {
                    this.RealTimeOnProcessClear(
                        // 实时数据处理，清理内存
                        buffers,
                        powerLevel,
                        bufferDuration,
                        bufferSampleRate,
                        newBufferIdx,
                        asyncEnd
                    );
                    if (this.durationCallBack) {
                        const deltaDuration = bufferDuration - this.lastBufferDuration;
                        this.lastBufferDuration = bufferDuration;
                        this.onLinePartDuration += deltaDuration;
                        this.durationCallBack(formatTime(this.onLinePartDuration), this.onLinePartDuration);
                    }
                    if (this.volumeCallBack) {
                        this.volumeCallBack(powerLevel);
                    }
                }, 1000),
                takeoffEncodeChunk: chunkBytes => {
                    // 接管实时转码，推入实时处理
                    this.RealTimeSendTry(chunkBytes, false);
                },
                audioTrackSet: { noiseSuppression: false, echoCancellation: false } // 关闭降噪，录制回音
                // onProcess: throttle((buffers, powerLevel, bufferDuration) => {
                //     this.RealTimeEncodeAndSend(false);
                // }, 1000)
            });
            this.rec.open(
                () => {
                    // 打开麦克风授权获得相关资源
                    // rec.start() 此处可以立即开始录音，但不建议这样编写，因为open是一个延迟漫长的操作，通过两次用户操作来分别调用open和start是推荐的最佳流程
                    resolve();
                },
                msg => {
                    // 用户拒绝未授权或不支持
                    // dialog&&dialog.Cancel(); 如果开启了弹框，此处需要取消
                    console.error(`无法录音: 请确认录音授权已开启(${msg})`);
                    reject();
                    // console.log(`${isUserNotAllow ? 'UserNotAllow，' : ''}无法录音:${msg}`);
                }
            );
        });
    }

    /**
     * 校验上传状态
     * contentLength 为已上传部分的长度
     */
    checkUploadState(contentLength) {
        if (this.cachedBlobClipArray.length) {
            this.uplodateStateChangeCallback(true); // 还需上传
            return;
        }
        this.uplodateStateChangeCallback(false); // 上传已结束
        if (this.recorderState === 0 || this.recorderState === 2) {
            this.onLinePartDuration = Math.round(contentLength / 2); // 在暂停 or 停止 状态下，同步线上部分的时间
            if (this.durationCallBack) {
                this.durationCallBack(formatTime(this.onLinePartDuration), this.onLinePartDuration);
            }
        }
    }

    /**
     * 重试上传
     */
    retryUpload = err => {
        console.error('XHR 报错，重传：', err);
        setTimeout(() => {
            this.startUpload(true); // 延时6s启动重传
        }, 6000);
    };

    /**
     * 开始上传
     * 错误处理逻辑为直接重传，直到传上去为止
     * @param {Boolean} isLoop 是否为递归调用，函数内部循环调用时使用。
     */
    async startUpload(isLoop = false) {
        if (this.uploadingPromise && !isLoop) {
            // 在上传服务运行时，非递归调用禁止往下走。
            console.log('在上传服务运行时，非递归调用禁止往下走。');
            return;
        }
        if (this.cachedBlobClipArray.length) {
            // 还有待上传数据
            let relationArr; // 首次上传，需要获取预授权，且需要在调用 uploadRecInfoFunc 时，增加资源关联用的数据。
            if (!this.ossKeyName) {
                this.ossKeyName = await getPreAuth({ name: this.ossFileName });
                relationArr = [
                    {
                        categoryCode: 'RecruitInterviewArrangeRecTemplateFile',
                        keyNameAndOpt: [`ADD:${this.ossKeyName}`]
                    }
                ];
            }
            const tempBlob = this.cachedBlobClipArray[0];
            const tempFile = {
                name: this.ossFileName,
                file: tempBlob,
                type: tempBlob.type
            };
            this.uploadingPromise = XHRUploadService(tempFile, 'OA', this.ossKeyName);
            this.uploadingPromise.then(
                ({ code, data }) => {
                    if (code === 10000) {
                        console.log('上传成功，删除已上传数据');
                        this.uploadRecInfoFunc(
                            this.ossKeyName,
                            this.recorderState === 0 ? 1 : 0,
                            // tempDuration,
                            Math.round(data / 2), // 使用返回的数据长度计算时长 16bit /s => 2 byte/s
                            0,
                            relationArr || null
                        );
                        relationArr = null;
                        this.cachedBlobClipArray.shift(); // 从数组中剔除已上传数据
                        this.checkUploadState(data);
                        this.startUpload(true); // 递归调用
                        this.checkFailedTimes();
                    } else {
                        // 上传接口报错
                        this.retryUpload(code);
                        this.checkFailedTimes(true);
                    }
                },
                err => {
                    // XMLHttpRequest 触发 onerror
                    this.retryUpload(err);
                    this.checkFailedTimes(true);
                }
            );
        } else {
            console.log('待上传队列已清空');
            this.uploadingPromise = null; // 待上传数组清空后，放开调用限制，允许重启上传服务
        }
    }
}

export default RealtimeUploader;
